package dbms;

import java.util.HashMap;
import java.util.Set;

import expression.ExpressionException;
import expression.Fn;
import expression.Generator;

public abstract class TableIterator {

	private static final int INSERT = 0, SELECT = 1, UPDATE = 2, DELETE = 3;

	private Table in, out;
	private int updateCount = 0;

	// CONSTRUCTOR

	private TableIterator(Table table, String[] clmn) {
		in = table;
		out = in.getSubTable(clmn);
	}

	public static TableIterator selectIterator(Table table, String[] outputColumns, String condition) throws DBException, ExpressionException {
		return getTableIterator(SELECT, table, outputColumns, condition, null, null);
	}

	public static TableIterator deleteIterator(Table table, String condition) throws DBException, ExpressionException {
		return getTableIterator(DELETE, table, null, condition, null, null);
	}

	public static TableIterator updateIterator(Table table, String condition, String[] setColumns, String[] values) throws DBException, ExpressionException {
		return getTableIterator(UPDATE, table, null, condition, setColumns, values);
	}

	//

	private static TableIterator getTableIterator(final int operation, final Table table, String[] outputColumns, String condition, final String[] setColumns,
		String[] values) throws DBException, ExpressionException {
		final HashMap<String, Fn> fns = new HashMap<String, Fn>();
		final HashMap<String, HashMap<String, Object>> maps = new HashMap<String, HashMap<String, Object>>();
		// fn's & maps
		if (operation == UPDATE && setColumns != null && values != null)
			for (int i = 0; i < values.length; i++) {
				Fn fn = Generator.generate(values[i]);
				fns.put(setColumns[i], fn);
				maps.put(setColumns[i], fn.getVariableMap());
			}
		TableIterator ti = new TableIterator(table, outputColumns) {
			@Override
			protected void set(int y) throws DBException, ExpressionException {
				if (operation == UPDATE)
					for (String colToBeSet : setColumns) {
						HashMap<String, Object> subMap = maps.get(colToBeSet);
						Set<String> colToSubFrom = subMap.keySet();
						for (String col : colToSubFrom)
							subMap.put(col, table.getColumn(col).getCell(y).getValue());
						System.out.println(fns.get(colToBeSet).evaluate(subMap));
						table.getColumn(colToBeSet).getCell(y).setValue(fns.get(colToBeSet).evaluate(subMap));
					}
			}
		};
		ti.filter(operation, condition);
		return ti;
	}

	private final <E> void filter(int operation, RowCondition rc) throws DBException, ExpressionException {
		for (int y = 0; y < out.getHeight(); y++) {
			if (rc.isGood(out, y)) {
				if (operation == DELETE) {
					remove(y--);
					updateCount++;
				} else if (operation == UPDATE) {
					updateCount++;
					set(y);
				}
			} else {
				if (operation == SELECT)
					out.removeRow(y--);
			}
		}
	}

	private final void filter(int operation, String condition) throws DBException, ExpressionException {
		final Fn fn = Generator.generate(condition);
		final HashMap<String, Object> map = fn.getVariableMap();
		final Set<String> colNames = map.keySet();
		filter(operation, new RowCondition() {
			@Override
			public boolean isGood(int y) throws DBException, ExpressionException {
				for (String colName : colNames)
					map.put(colName, getField(colName).getValue());
				return (boolean) fn.evaluate(map);
			}
		});
	}

	protected abstract void set(int y) throws DBException, ExpressionException;

	private final void remove(int y) {
		try {
			in.removeRow(y);
		} catch (DBException e) {}
	}

	private Table getInput() {
		return in;
	}

	public final Table getOutput() {
		return out;
	}

	public final int getUpdateCount() {
		return updateCount;
	}

}